package blazingbubble.legacy.game;

import java.util.Collection;
import java.util.Iterator;
import java.util.Vector;

import spark.math.Vector3;
import spark.math.Random;
import spark.utility.Clock;

public class Bubble
{
    public static enum Type
    {
        Red(0),
        Green(1),
        Blue(2),
        Yellow(3),
        Violet(4),
        Orange(5),
        Black(6),
        White(7),
        Rainbow(8),
        Blazing(9),
        Ghost(10),
        Lightning(11);

        private int type;
        private static final int COUNT = Lightning.type + 1;
        private static final int COLORED_BUBBLE_START_INDEX = Red.type;
        private static final int COLORED_BUBBLE_END_INDEX = White.type;

        private Type(int type)
        {
            this.type = type;
        }

        public boolean isColored()
        {
            return type >= COLORED_BUBBLE_START_INDEX && type <= COLORED_BUBBLE_END_INDEX;
        }

        public static Type getRandomColoredType()
        {
            int desiredType = Random.getInstance().nextInt(COLORED_BUBBLE_END_INDEX + 1) + COLORED_BUBBLE_START_INDEX;
            if(desiredType == Red.type)
                return Red;
            else if(desiredType == Green.type)
                return Green;
            else if(desiredType == Blue.type)
                return Blue;
            else if(desiredType == Yellow.type)
                return Yellow;
            else if(desiredType == Violet.type)
                return Violet;
            else if(desiredType == Orange.type)
                return Orange;
            else if(desiredType == White.type)
                return White;
            else if(desiredType == Black.type)
                return Black;
            throw new RuntimeException("Colored bubble type does not exist: " + desiredType);
        }

        @Override
        public String toString()
        {
            if(this == Red)
                return "Red";
            else if(this == Green)
                return "Green";
            else if(this == Blue)
                return "Blue";
            else if(this == Yellow)
                return "Yellow";
            else if(this == Violet)
                return "Violet";
            else if(this == Orange)
                return "Orange";
            else if(this == White)
                return "White";
            else if(this == Black)
                return "Black";
            else if(this == Rainbow)
                return "Rainbow";
            else if(this == Blazing)
                return "Blazing";
            else if(this == Lightning)
                return "Lightning";
            else if(this == Ghost)
                return "Ghost";
            return "Unknown Type";
        }

        public static Iterator<Type> iterator()
        {
            return new TypeIterator();
        }

        public static class TypeIterator implements Iterator<Type>
        {
            private static Type[] types = new Type[COUNT];
            private int i = 0;
            static
            {
                types[Red.type] = Red;
                types[Green.type] = Green;
                types[Blue.type] = Blue;
                types[Yellow.type] = Yellow;
                types[Violet.type] = Violet;
                types[Orange.type] = Orange;
                types[Black.type] = Black;
                types[White.type] = White;
                types[Rainbow.type] = Rainbow;
                types[Blazing.type] = Blazing;
                types[Ghost.type] = Ghost;
                types[Lightning.type] = Lightning;
            }
            public boolean hasNext()
            {
                return (i < COUNT);
            }
            public Type next()
            {
                return types[i++];
            }
            public void remove()
            {
                throw new RuntimeException("Cant remove from this iterator");
            }
        }
    }
    private static final float RADIUS = 0.5f;
    private static final float COLLISION_RADIUS = RADIUS * 0.85f;
    public static final float COLLISION_TOLEANCE = 0.000000001f;
    private boolean isSupersized = false;
    private Vector3 position = new Vector3();
    private Vector3 velocity = new Vector3();
    private Vector<Bubble> attached = new Vector<Bubble>();
    private int gridPositionX = -1;
    private int gridPositionY = -1;
    private Player owner;
    private boolean directlyAttachedToWorld = false;
    private Type type;

    public boolean debug = false;

    public Bubble(Vector3 position, Player owner, Type type)
    {
        this.type = type;
        this.position = position;
        this.owner = owner;
    }

    public void disattach()
    {
        for(Bubble currentBubble : attached)
        {
            currentBubble.attached.remove(this);
        }
        attached.clear();
    }

    public void attach(Bubble bubble)
    {
        attached.add(bubble);
        bubble.attached.add(this);
    }

    public BubbleChain getBubbleChain()
    {
        BubbleChain chain = new BubbleChain();
        getBubbleChain(chain, this.getType(), new Vector<Bubble>());
        if(type == Type.Rainbow)
        {
            Vector<Bubble> surroundingBubbles = getSurroundingBubbles(chain.getChainBubblesByCriticalMass(0));
            Vector<Type> typesContained = new Vector<Type>();
            for(Bubble currentBubble : surroundingBubbles)
            {
                if(!currentBubble.getType().isColored())
                {
                    continue;
                }
                if(typesContained.contains(currentBubble.getType()))
                {
                    continue;
                }
                currentBubble.getBubbleChain(chain, currentBubble.getType(), new Vector<Bubble>());
                typesContained.add(currentBubble.getType());
            }
        }
        return chain;
    }

    private void getBubbleChain(BubbleChain chain, Type type, Vector<Bubble> checked)
    {
        if(checked.contains(this))
            return;
        checked.add(this);
        if(!chain.nextToChain(this) && !chain.isEmpty())
        {
            return;
        }
        if(this.type == type || this.type == Type.Rainbow)
        {
            chain.add(this, type);
        }
        else
            return;
        for(Bubble anAttached : attached)
        {
            anAttached.getBubbleChain(chain, type, checked);
        }
    }

    public float getCollisionTime(Bubble bubble)
    {
        float A = 	this.velocity.x * this.velocity.x + this.velocity.y * this.velocity.y - 2 * this.velocity.x * bubble.velocity.x + bubble.velocity.x * bubble.velocity.x - 2 * this.velocity.y * bubble.velocity.y + bubble.velocity.y * bubble.velocity.y;
        float B = 	-this.position.x * this.velocity.x - this.position.y * this.velocity.y + this.velocity.x * bubble.position.x + this.velocity.y * bubble.position.y + this.position.x * bubble.velocity.x -
                      bubble.position.x * bubble.velocity.x + this.position.y * bubble.velocity.y - bubble.position.y * bubble.velocity.y;
        float C = 	this.velocity.x * this.velocity.x + this.velocity.y * this.velocity.y - 2 * this.velocity.x * bubble.velocity.x + bubble.velocity.x * bubble.velocity.x - 2 * this.velocity.y * bubble.velocity.y + bubble.velocity.y * bubble.velocity.y;
        float D =  	this.position.x * this.position.x + this.position.y * this.position.y - this.getCollisionRadius() * this.getCollisionRadius() - 2 * this.position.x * bubble.position.x + bubble.position.x * bubble.position.x - 2 * this.position.y * bubble.position.y +
                       bubble.position.y * bubble.position.y - 2 * this.getCollisionRadius() * bubble.getCollisionRadius() - bubble.getCollisionRadius() * bubble.getCollisionRadius();
        float DISC = (-2 * B) * (-2 * B) - 4 * C * D;

        float t = (float)Math.min(Math.min(Double.MAX_VALUE, 0.5 * (2 * B - Math.sqrt(DISC)) / A), 0.5 * (2 * B + Math.sqrt (DISC)) / A);
        return t;
    }

    public boolean willCollide(Bubble bubble)
    {
        return willCollide(this, bubble);
    }

    public static boolean willCollide(Bubble first, Bubble second)
    {
        if(!movingTowardsEachOther(first, second))
            return false;

        Vector3 firstFuturePosition = Vector3.add(first.position, Vector3.multiply(first.velocity, Clock.getInstance().getElapsedSeconds()));
        Vector3 secondFuturePosition = Vector3.add(second.position, Vector3.multiply(second.velocity, Clock.getInstance().getElapsedSeconds()));

        return Vector3.substract(secondFuturePosition, firstFuturePosition).lengthWithoutSquareRoot() <=
               (Math.pow(first.getCollisionRadius() + second.getCollisionRadius(), 2.0) + COLLISION_TOLEANCE);
    }

    public Vector<Bubble> gatherBubbleMass()
    {
        Vector<Bubble> mass = new Vector<Bubble>();
        gatherBubbleMass(mass);
        return mass;
    }

    private void gatherBubbleMass(Vector<Bubble> mass)
    {
        if(mass.contains(this))
            return;
        mass.add(this);
        for(Bubble anAttached : attached)
        {
            anAttached.gatherBubbleMass(mass);
        }
    }

    public boolean isHangingFree()
    {
        Vector<Bubble> checked = new Vector<Bubble>();
        return isHangingFree(checked);
    }

    private boolean isHangingFree(Vector<Bubble> checked)
    {
        if(checked.contains(this))
        {
            return true;
        }
        else if(getDirectlyAttachedToWorld())
        {
            return false;
        }
        else
        {
            checked.add(this);
            for(Bubble anAttached : attached)
            {
                if(!anAttached.isHangingFree(checked))
                {
                    return false;
                }
            }
        }
        return true;
    }

    public boolean movingTowardsEachOther(Bubble bubble)
    {
        return movingTowardsEachOther(this, bubble);
    }

    public static boolean movingTowardsEachOther(Bubble first, Bubble second)
    {
        Vector3 velocityDifference = Vector3.substract(first.velocity, second.velocity);
        Vector3 positionDifference = Vector3.substract(second.position, first.position);

        return (Vector3.dot(velocityDifference, positionDifference) > 0);
    }

    public static Vector<Bubble> getSurroundingBubbles(Collection<Bubble> centerBubbles)
    {
        Vector<Bubble> surroundingBubbles = new Vector<Bubble>();
        Vector<Bubble> centerBubbleCopy = new Vector<Bubble>(centerBubbles);
        for(Bubble currentBubble : centerBubbles)
        {
            surroundingBubbles.removeAll(currentBubble.getAttached());
            surroundingBubbles.addAll(currentBubble.getAttached());
        }
        surroundingBubbles.removeAll(centerBubbleCopy);
        return surroundingBubbles;
    }

    public Type getType() { return type; }
    public Vector3 getVelocity() { return velocity; }
    public void setVelocity(Vector3 velocity) { this.velocity = velocity; }
    public Vector3 getPosition() { return position; }
    public void setPosition(Vector3 position) { this.position = position; }
    public Player getOwner() { return owner; }
    public boolean isSupersized() { return isSupersized; }
    public float getCollisionRadius() { return COLLISION_RADIUS; }
    public static float getRadius() { return RADIUS; }
    public int getGridPositionX() { return gridPositionX; }
    public void setGridPositionX(int gridPositionX) { this.gridPositionX = gridPositionX; }
    public int getGridPositionY() { return gridPositionY; }
    public void setGridPositionY(int gridPositionY) { this.gridPositionY = gridPositionY; }
    public void setDirectlyAttachedToWorld(boolean directlyAttachedToWorld) { this.directlyAttachedToWorld = directlyAttachedToWorld; }
    public boolean getDirectlyAttachedToWorld() { return directlyAttachedToWorld; }
    public Vector<Bubble> getAttached() { return attached; }
}
